package redis.clients.jedis;

import java.net.URI;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;

import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.util.JedisURIHelper;

public class JedisPool extends JedisPoolAbstract {

    private static final Logger log = LoggerFactory.getLogger(JedisPool.class);

    public JedisPool() {
        this(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);
    }

    /**
     * WARNING: This constructor will be changed to accept a uri string in next major release.
     *
     * @param poolConfig
     * @param host
     * @deprecated This constructor will be changed to accept a uri string in next major release.
     */
    @Deprecated
    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host) {
        this(poolConfig, host, Protocol.DEFAULT_PORT);
    }

    public JedisPool(String host, int port) {
        this(new GenericObjectPoolConfig<Jedis>(), host, port);
    }

    /**
     * @param url
     * @deprecated This constructor will not accept a host string in future. It will accept only a uri
     * string. You can use {@link JedisURIHelper#isValid(java.net.URI)} before this.
     */
    @Deprecated
    public JedisPool(final String url) {
        URI uri = URI.create(url);
        if (JedisURIHelper.isValid(uri)) {
            initPool(new GenericObjectPoolConfig<Jedis>(), new JedisFactory(uri,
                    Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, null));
        } else {
            initPool(new GenericObjectPoolConfig<Jedis>(), new JedisFactory(url, Protocol.DEFAULT_PORT,
                    Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, null, Protocol.DEFAULT_DATABASE, null));
        }
    }

    /**
     * @param url
     * @param sslSocketFactory
     * @param sslParameters
     * @param hostnameVerifier
     * @deprecated This constructor will not accept a host string in future. It will accept only a uri
     * string. You can use {@link JedisURIHelper#isValid(java.net.URI)} before this.
     */
    @Deprecated
    public JedisPool(final String url, final SSLSocketFactory sslSocketFactory,
                     final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
        URI uri = URI.create(url);
        if (JedisURIHelper.isValid(uri)) {
            initPool(new GenericObjectPoolConfig<Jedis>(), new JedisFactory(uri,
                    Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, null, sslSocketFactory,
                    sslParameters, hostnameVerifier));
        } else {
            initPool(new GenericObjectPoolConfig<Jedis>(), new JedisFactory(url, Protocol.DEFAULT_PORT,
                    Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, null, Protocol.DEFAULT_DATABASE, null,
                    false, null, null, null));
        }
    }

    public JedisPool(final URI uri) {
        this(new GenericObjectPoolConfig<Jedis>(), uri);
    }

    public JedisPool(final URI uri, final SSLSocketFactory sslSocketFactory,
                     final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
        this(new GenericObjectPoolConfig<Jedis>(), uri, sslSocketFactory, sslParameters,
                hostnameVerifier);
    }

    public JedisPool(final URI uri, final int timeout) {
        this(new GenericObjectPoolConfig<Jedis>(), uri, timeout);
    }

    public JedisPool(final URI uri, final int timeout, final SSLSocketFactory sslSocketFactory,
                     final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
        this(new GenericObjectPoolConfig<Jedis>(), uri, timeout, sslSocketFactory, sslParameters,
                hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String password) {
        this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE);
    }

    public JedisPool(final String host, int port, String user, final String password) {
        this(new GenericObjectPoolConfig<Jedis>(), host, port, user, password);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     String user, final String password) {
        this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, user, password,
                Protocol.DEFAULT_DATABASE);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String user, final String password) {
        this(poolConfig, host, port, timeout, user, password, Protocol.DEFAULT_DATABASE);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String password, final boolean ssl) {
        this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE, ssl);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String user, final String password, final boolean ssl) {
        this(poolConfig, host, port, timeout, user, password, Protocol.DEFAULT_DATABASE, ssl);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String password, final boolean ssl,
                     final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
                     final HostnameVerifier hostnameVerifier) {
        this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE, ssl,
                sslSocketFactory, sslParameters, hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,
                     final int port) {
        this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,
                     final int port, final boolean ssl) {
        this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, ssl);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,
                     final int port, final boolean ssl, final SSLSocketFactory sslSocketFactory,
                     final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
        this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, ssl, sslSocketFactory, sslParameters,
                hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,
                     final int port, final int timeout) {
        this(poolConfig, host, port, timeout, null);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,
                     final int port, final int timeout, final boolean ssl) {
        this(poolConfig, host, port, timeout, null, ssl);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,
                     final int port, final int timeout, final boolean ssl,
                     final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
                     final HostnameVerifier hostnameVerifier) {
        this(poolConfig, host, port, timeout, null, ssl, sslSocketFactory, sslParameters,
                hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String password, final int database) {
        this(poolConfig, host, port, timeout, password, database, null);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String user, final String password, final int database) {
        this(poolConfig, host, port, timeout, user, password, database, null);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String password, final int database, final boolean ssl) {
        this(poolConfig, host, port, timeout, password, database, null, ssl);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String user, final String password, final int database, final boolean ssl) {
        this(poolConfig, host, port, timeout, user, password, database, null, ssl);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String password, final int database, final boolean ssl,
                     final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
                     final HostnameVerifier hostnameVerifier) {
        this(poolConfig, host, port, timeout, password, database, null, ssl, sslSocketFactory,
                sslParameters, hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String password, final int database, final String clientName) {
        this(poolConfig, host, port, timeout, timeout, password, database, clientName);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String user, final String password, final int database,
                     final String clientName) {
        this(poolConfig, host, port, timeout, timeout, user, password, database, clientName);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String password, final int database, final String clientName,
                     final boolean ssl) {
        this(poolConfig, host, port, timeout, timeout, password, database, clientName, ssl);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String user, final String password, final int database,
                     final String clientName, final boolean ssl) {
        this(poolConfig, host, port, timeout, timeout, user, password, database, clientName, ssl);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     int timeout, final String password, final int database, final String clientName,
                     final boolean ssl, final SSLSocketFactory sslSocketFactory,
                     final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
        this(poolConfig, host, port, timeout, timeout, password, database, clientName, ssl,
                sslSocketFactory, sslParameters, hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     final int connectionTimeout, final int soTimeout, final String password, final int database,
                     final String clientName, final boolean ssl, final SSLSocketFactory sslSocketFactory,
                     final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
        this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, password,
                database, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier));
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
                     final String password, final int database, final String clientName, final boolean ssl,
                     final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
                     final HostnameVerifier hostnameVerifier) {
        this(poolConfig, host, port, connectionTimeout, soTimeout, infiniteSoTimeout, null, password,
                database, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     final int connectionTimeout, final int soTimeout, final String user, final String password,
                     final int database, final String clientName, final boolean ssl,
                     final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
                     final HostnameVerifier hostnameVerifier) {
        this(poolConfig, host, port, connectionTimeout, soTimeout, 0, user, password, database,
                clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
                     final String user, final String password, final int database, final String clientName,
                     final boolean ssl, final SSLSocketFactory sslSocketFactory,
                     final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {

        this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, infiniteSoTimeout,
                user, password, database, clientName, ssl, sslSocketFactory, sslParameters,
                hostnameVerifier));
    }

    public JedisPool(final HostAndPort hostAndPort, final JedisClientConfig clientConfig) {
        this(new GenericObjectPoolConfig<Jedis>(), hostAndPort, clientConfig);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final HostAndPort hostAndPort,
                     final JedisClientConfig clientConfig) {
        this(poolConfig, new JedisFactory(hostAndPort, clientConfig));
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final JedisSocketFactory jedisSocketFactory,
                     final JedisClientConfig clientConfig) {
        this(poolConfig, new JedisFactory(jedisSocketFactory, clientConfig));
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig) {
        this(poolConfig, Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT);
    }

    public JedisPool(final String host, final int port, final boolean ssl) {
        this(new GenericObjectPoolConfig<Jedis>(), host, port, ssl);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     final int connectionTimeout, final int soTimeout, final String password, final int database,
                     final String clientName) {
        this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, password,
                database, clientName));
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     final int connectionTimeout, final int soTimeout, final String user, final String password,
                     final int database, final String clientName) {
        this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, user, password,
                database, clientName));
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host, int port,
                     final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
                     final String user, final String password, final int database, final String clientName) {
        this(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, infiniteSoTimeout,
                user, password, database, clientName));
    }

    public JedisPool(final String host, final int port, final boolean ssl,
                     final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
                     final HostnameVerifier hostnameVerifier) {
        this(new GenericObjectPoolConfig<Jedis>(), host, port, ssl, sslSocketFactory, sslParameters,
                hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,
                     final int port, final int connectionTimeout, final int soTimeout, final String password,
                     final int database, final String clientName, final boolean ssl) {
        this(poolConfig, host, port, connectionTimeout, soTimeout, password, database, clientName, ssl,
                null, null, null);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final String host,
                     final int port, final int connectionTimeout, final int soTimeout, final String user,
                     final String password, final int database, final String clientName, final boolean ssl) {
        this(poolConfig, host, port, connectionTimeout, soTimeout, user, password, database,
                clientName, ssl, null, null, null);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri) {
        this(poolConfig, uri, Protocol.DEFAULT_TIMEOUT);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,
                     final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
                     final HostnameVerifier hostnameVerifier) {
        this(poolConfig, uri, Protocol.DEFAULT_TIMEOUT, sslSocketFactory, sslParameters,
                hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri, final int timeout) {
        this(poolConfig, uri, timeout, timeout);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,
                     final int timeout, final SSLSocketFactory sslSocketFactory,
                     final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
        this(poolConfig, uri, timeout, timeout, sslSocketFactory, sslParameters, hostnameVerifier);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,
                     final int connectionTimeout, final int soTimeout) {
        this(poolConfig, uri, connectionTimeout, soTimeout, null, null, null);
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,
                     final int connectionTimeout, final int soTimeout, final SSLSocketFactory sslSocketFactory,
                     final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) {
        this(poolConfig, new JedisFactory(uri, connectionTimeout, soTimeout, null, sslSocketFactory,
                sslParameters, hostnameVerifier));
    }

    public JedisPool(final GenericObjectPoolConfig<Jedis> poolConfig, final URI uri,
                     final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
                     final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
                     final HostnameVerifier hostnameVerifier) {
        this(poolConfig, new JedisFactory(uri, connectionTimeout, soTimeout, infiniteSoTimeout, null,
                sslSocketFactory, sslParameters, hostnameVerifier));
    }


    /**
     * JedisPool初始化并不会立即创建所有jedis实例。
     * 在定义了JedisPool并设置相关参数，如最大连接数、最大空闲数、最小空闲数等后,池中并没有立即创建
     * 对应的Jedis连接。
     * 初次使用时，如果池中没有可用资源，那么就会新建一个jedis实例来满足需求，并在使用完毕后将其放到资源池。
     * 这种方式可以避免频繁创建大量socket连接，从而提高性能。
     * <p>
     * 此外，为了节省资源，使用JedisPool是一种很好的选择，你可以初始化的时候先创建一些Jedis实例，以提高响应速度。
     * 当使用完成Jedis连接对象后，建议将其归还给连接池，即调用Jedis的close方法，这样不仅可以降低资源消耗，还方便管理和维护
     */
    public JedisPool(GenericObjectPoolConfig poolConfig, PooledObjectFactory<Jedis> factory) {
        super(poolConfig, factory);
    }


    /**
     * 从连接池中获取一个可用的 Redis 连接,然后使用该连接对象执行相应的命令。
     * <p>
     * 当连接被使用时，getResource() 方法会将连接标记为正在使用状态，并返回给调用者。如果连接池中的连接已经全部被占用，那么该方法会阻塞等待直到有可用的连接为止。
     * 当连接不再使用时，可以将其归还给连接池，以便其他线程或进程可以使用。这样可以确保连接池中的连接始终处于可用状态，并且能够有效地复用和管理。
     */
    @Override
    public Jedis getResource() {
        Jedis jedis = super.getResource();
        jedis.setDataSource(this);
        return jedis;
    }

    @Override
    public void returnResource(final Jedis resource) {
        if (resource != null) {
            try {
                resource.resetState();
                returnResourceObject(resource);
            } catch (RuntimeException e) {
                returnBrokenResource(resource);
                log.warn("Resource is returned to the pool as broken", e);
            }
        }
    }
}
